Temperature control system

1.Experimental setup

A temperature sensor, a heater and a cooler are to be connected to a Arduino board. In order for it to work we need to have shifters for the voltage range to fit the inputs/outputs on the Arduino. This was first done by Vanessa Scheller using cables and a breadboard, I took her design and reworked it to be used on a milled circuit board and SMD parts.

1.1. Temperature sensor circuit

The circuit for the temperature sensor looks as follows: image Fig. 1: temp. sensor shifter circuit

The input range from the temperature sensor we are interested in is $$-1.6V < U_{in} < 1.6V$$ The supply voltage of the op-amps is 15V. We want to have a cutoff after the first op-amp at the input range boundaries. This can be achieved with setting the amplification factor with $R_1$ and $R_2$. The output range should equal the input range of the Arduino analogue input which goes form 0-3.3V. The values found for the resistances are as follows (in $k\Omega$):

In [60]:
R1 = 15
R2 = 133
R3 = 133
R4 = 12 + 2.5 #5kOhm pot.
R5 = 10.0
R6 = 15 + 2.5 #5KOhm pot.

The formula for the output voltage with the voltage from REF03 being 2.5V:

In [61]:
def Uout(Uin):
    if Uin < -1.6:
        Uin = -1.6
    if Uin > 1.6:
        Uin = 1.6
    return 2.5*R6*(R4+R3)/(R5+R6)/R3 + Uin*R2*R4/R1/R3

This gives to following theoretical response:

In [62]:
from fitting import FuncPlot
from plotly.offline import init_notebook_mode,iplot
import plotly.graph_objs as go
init_notebook_mode(connected=True)
plot = FuncPlot(Uout, -2, 2)
iplot(plot.getPlotData(title="Voltage curve", x_axis="U_in[V]", y_axis="U_out[V]"), show_link = False)

Graph 1: theoretical response

1.2. Heater, cooler circuit

image Fig. 2: heater/cooler shifter circuit

The output range from the Arduino's DAC is $$0.55V < U_{DAC} < 2.75V$$ The regulation voltage from the heater and cooler is 0 to 10V. First the voltage from the DAC out needs to be reduced by 0.55V and then amplified such that the maximum out from the DAC corresponds to the maximum in from the heater/cooler. The amplification is set with the resistance RG. The values found by Vanessa are (in $k\Omega$):

In [63]:
Ra = 3.92 + 2.5 #5kOhm pot
Rb = 1.0
RG = 27 + 5 #10kOhm pot.

The output here can be described by the amplified difference of the two input voltages. The amplification factor was adjusted such that it gives the needed output range.

To both circuits many capacitances are added, which are only for filtering the input +15V/-15V and the ref03 voltages from AC parts. This is done with 10nF and 100nF capacitances in parallel.

1.3. Realization of the circuitboard

All of the circuits were realized using a smd board. The circuitboard was constructed using EAGLE Professional and made with a circuit board cutter. It has the same proportions as the used arduino due, such that it fits into the enclosure thats already existing.

image Fig. 3: Circuitboard layout

The resistors and the capacitances are directly soldered onto the board, while the OP07 and the AD622 (not 620 as on the image) are put onto headers. The complete board is shown here:

image Fig. 4: Circuitboard picture

Notice that the header for the left AD622 was placed in the opposite direction, such that the marker for the top is actually on the other side than the top is supposed to be.
Also some of the capacitances were not soldered in, on the design of the board they were made as a precaution.
It was found that the heater/cooler cables add a capacitance which lead to oscillations on the temperature measurement. We added $100 \Omega$ resistors on the heater/cooler output to resolve the issue.
Below is a picture of the board connected to the Arduino with the Ethernet Shield 2 on top.

image Fig.5: Board connected to Arduino

What might need to be changed is:

  • The power supply of the Arduino is possibly not strong enough, as the outputs do supply voltages slightly lower than intended (DAC output). The power supply has around 6.1V in through the power jack. Directly using the Vin input of the Arduino does help (tested seperatly)
  • The temperature input on A0 could be secured with diodes, because to high voltages might break the Arduino
  • The signal from the temperature sensor oscillates → build a filter? → done

After everything on the board is done I put everything into the housing in the cabinet.
image Fig. 6: Board and Arduino in the housing

2. Measurements

2.1. Measurements for the heater/cooler shifter circuit

We measure the output voltage from the heater and cooler shifter while changing the input through the Arduino DAC outputs. The DAC outputs use 8Bit resolution, so we measure from 0 to 255 Bits.

In [64]:
import numpy as np
x       = np.array([    0,    25,    50,    75,   100,   125,   150,  175,  200,  225,  250,   255])
y_oben  = np.array([0.011, 0.997, 1.983, 2.969, 3.955, 4.941, 5.929, 6.91, 7.90, 8.89, 9.87, 10.07])
y_unten = np.array([0.002, 0.982, 1.961, 2.942, 3.921, 4.902, 5.883, 6.86, 7.84, 8.82, 9.80, 10.00])
In [65]:
from fitting import Fit
y_error_oben  = np.vectorize((lambda x: 0.01 + 0.01*x if x > 6.0 else 0.001 + 0.01*x))(y_oben)
y_error_unten = np.vectorize((lambda x: 0.01 + 0.01*x if x > 6.0 else 0.001 + 0.01*x))(y_oben)

#linear fit function
def linear_fit(x,m,c):
    return m*x+c

oben = Fit(x, y_oben,y_error_oben)
oben.curvefit(linear_fit)
iplot(oben.getPlotData(title="Top shifter", x_axis="Bits", y_axis="U_out[V]"), show_link = False)

unten = Fit(x, y_unten,y_error_unten)
unten.curvefit(linear_fit)
iplot(unten.getPlotData(title="Bottom shifter", x_axis="Bits", y_axis="U_out[V]"), show_link = False)

Graph 2: heater/cooler shifter

It's visible that the components act as expected, i.e. linearity is achieved. The minimum out voltages are near enough to 0 and the maximum voltages are close enough to 10V.

2.2. Measurements of temperature sensor circuit

We first measure the voltages (and set them with the variable resistances) on the output for the min/max values -1.6V/1.6V and make sure that above/below these voltages the output voltage does not change anymore. Then we measure the values inbetween.

In [66]:
Uin  = np.array([ -5.0,   2.5,  -1.9,  -1.6,  -1.4,  -1.2,  -1.0,  -0.8,  -0.6,  -0.4 , -0.2,   0.0,   0.2,   0.4,   0.6,   0.8,   1.0,   1.2,   1.4,   1.6,   1.9,   2.5,   5.0])
Uout = np.array([0.001, 0.000, 0.000, 0.009, 0.219, 0.428, 0.637, 0.846, 1.056, 1.265, 1.474, 1.684, 1.894, 2.103, 2.312, 2.522, 2.731, 2.941, 3.150, 3.246, 3.244, 3.246, 3.245])
In [67]:
Uout_err = (lambda x: 0.01*x + 0.001)(Uout)
temp = Fit(Uin,Uout,Uout_err)
temp.curvefit(linear_fit, bounds = (3,-4))
iplot(temp.getPlotData(title="temp shifter", x_axis="U_in[V]", y_axis="U_out[V]"), show_link = False)

Graph 3: response of the temp. shifter

We see that we have linearity between -1.6V and 1.6V (slight drop before 1.6V) and afterwards the desired plateaus.

3. The temperature control system

3.1. Controlling the PID

The temperature value gets read out on a arduino due platform. It then checks with the temperature reference and then calculates control values for the heater / cooler with a PID controller, which was realized as a software running on the arduino. This was originally developed by Vanessa Scheller. I worked with her log book and report.
One of my tasks was to install the Ethernet Shield 2 on the Arduino in order for the PID controller to be abled to communicate more easily with a computer. The existing possibility to communicate with the Arduino was to do it over the usb serial interface of the Arduino which allows to read out data and also send data to the Arduino, but requires a computer to be connected to the Arduino at all times. The advantage of a network interface is that it just needs a connected ethernet cable to the Arduino.
The Ethernet Shield 2 is just connected to the Arduino like any other Arduino shield, by just plugging it on top of the Arduino. The Arduino acts as a server which communicates with a client program running on the tritium-vserver. The client sends commands directly to the Arduino which get handled there in a defined time intervall. The Arduino sends back a response including information.
The client program runs in the background and issues the command to receive the serial output from the arduino and stores those directly in a file as is, but also creates a THee file with only the time and the temperature values.
The client program can be controlled with the controller script. Running the script with a command line argument makes the client program send the argument as a command to the Arduino. The answer from the arduino is stored in a log file and also printed on the console.

Getting the serial output
Send the command g to receive the serial output. The values received are:

  1. Measurement number (resets every new running day)
  2. Mean temperature value over 1 minute. (in 10 bit resolution from 0 to 1023)
  3. Difference of current temperature to set value of temperature
  4. Difference of mean temperature to set value of temperature
  5. Heater set value (in 8 bit resolution from 0 to 255)
  6. Cooler set value (in 8 bit resolution from 0 to 255)
  7. Days running

Getting the PID parameters and the temperature set value
Send the command gpid to receive the values $K_P$, $T_N$ and $T_V$ for both the heater and the cooler as well as the current temperature set value (which is titled sollwert). Also get the state of the PID (1 for turned on, 0 for turned off).

Setting the PID parameters and setting fixed heater/cooler set values
Send the command xy###, where x is either h or c for heater/cooler and y is either p,i,d for the respective part of the regulator. y can also be f for a fixed value of the heater/cooler set value (this turns of the PID control). ### corresponds to the wanted value (again in 8 bits from 0 to 255). The Arduino programm checks whether the value is in bounds.

Setting the temperature reference
Send the command so### to set the temperature reference to the value ### (in 10 bits).

Turning PID off and back on
Send the command pidon / pidoff to turn the PID on or off.

The calibration for the temperature values of the sensor is as follows:

In [68]:
def calib(bit):
    return  0.0253714*bit + 12.0968

plot = FuncPlot(calib, 0, 1023)
iplot(plot.getPlotData(title="Temperature calibration", x_axis="Bit", y_axis="Temp [°C]", 
                       fitres = 1024), show_link = False)

Graph 4: Temperature calibration

3.2. Testing the temperature control system

The board is now connected to the arduino, the temperature sensor and the heater/cooler. Now I measure for a constant temperature reference the temperature with already existing sensors (i.e. top window, top door and the pid temperature sensor).

3.2.1. Holding the temperature constant

We want to see now, how well the temperature stabilization works for keeping the temperature constant over the day (18.02.2017). We get the data for one day from the THee-Files located in the tritium-drive (the pid-client program also produces Thee-files there).
Also a histogram of the data at the PID-sensor is plotted.

In [69]:
from THeePlot import THeePlot

calib_pid = lambda y: 0.0253714*y + 12.0968
plot1 = THeePlot("./../data/T_PID_2017-02-18.THee", calib_pid, 1)
iplot(plot1.getPlotData(title="T_PID", x_axis="Time [h]", y_axis="Temp [°C]",connected = True), show_link = False)

calib_door = lambda y: -0.001827*y + 27.346001
plot2 = THeePlot("./../data/T_Top_Door_2017-02-18.THee", calib_door, 10)
iplot(plot2.getPlotData(title="T_Top_Door", x_axis="Time [h]", y_axis="Temp [°C]",connected = True), show_link = False)

calib_window = lambda y: -0.001821*y + 27.933001
plot3 = THeePlot("./../data/T_Top_Window_2017-02-18.THee", calib_window, 10)
iplot(plot3.getPlotData(title="T_Top_Window", x_axis="Time [h]", y_axis="Temp [°C]",connected = True), show_link = False)

from fitting import Hist
yval = np.array(plot1.getPlotData()['data'][0]['y'])
plot4 = Hist(yval, binsize = 0.01)
iplot(plot4.getPlotData(), show_link = False)
print("Mean = {:.3f}".format(np.mean(yval)) + "K , " + " std = {:.3f}".format(np.mean(std)) + "K")
Mean = 23.515K ,  std = 0.057K

Graph 5: Temperature stabilization over one day with temp. from sensors T_PID, T_Top_Door, T_Top_Window

We see that for the sensor the PID uses to regulate we have a good nearly constant temperature (fluctuations $\sim 0.5 °C$ and $\sigma = 0.057K$). The other two sensor show a fast rise(window) and a fall(door) in temperature at around 12 o'clock. I can now look at the cooler/heater values and see that the cooler was shut off up until nearly 12 o'clock and then turned on (probably due to rise in outside temperature), while the heater value decreased.
The heater is located closer to the window and the cooler closer to the door. The rise in outside temperature could be due to sun coming in the windows thus raising the temperature there fast. The drop at the door should then be due to the cooler being turned on.

3.2.2. Change in temperature reference

To see how the system reacts to rapid changes in temperature it is much easier to look at a change in the reference temperature. For this we change the value of the reference temperature from 450 Bits ($\sim 23.5 °C$) to 500 Bits ($\sim 24.8 °C$).
Again we look at the temperature values from the 3 sensors.

In [70]:
calib_pid = lambda y: 0.0253714*y + 12.0968
plot1 = THeePlot("./../data/T_PID_2017-02-20.THee", calib_pid, 1)
iplot(plot1.getPlotData(title="T_PID", x_axis="Time [h]", y_axis="Temp [°C]",connected = True,
                       x_range = [8,16]), show_link = False)

calib_door = lambda y: -0.001827*y + 27.346001
plot2 = THeePlot("./../data/T_Top_Door_2017-02-20.THee", calib_door, 10)
iplot(plot2.getPlotData(title="T_Top_Door", x_axis="Time [h]", y_axis="Temp [°C]",connected = True,
                       x_range = [8,16]), show_link = False)

calib_window = lambda y: -0.001821*y + 27.933001
plot3 = THeePlot("./../data/T_Top_Window_2017-02-20.THee", calib_window, 10)
iplot(plot3.getPlotData(title="T_Top_Window", x_axis="Time [h]", y_axis="Temp [°C]",connected = True,
                       x_range = [8,16]), show_link = False)

Graph 6: response to temperature reference change on the different sensor

We can see that the temperature at the door and at the PID sensor react very similarly, the temperature at the window seems to react a little slower.


We can now compute the relaxation time of the system with the following fit:

$ f(t) = B + A(1-e^{-t/\tau}) $

for the upwards slope and

$ f(t) = B + A e^{-t/\tau} $

for the downwards slope For the fit to work we need errors for the temperature values. For this we assume a constant error of 0.1 °C. The bounds are set to the time the temperature reference was set.

In [71]:
f = open("./../data/T_PID_2017-02-20.THee")
f.readline(); f.readline(); f.readline()
x,y = [],[]
for line in f:
    a,b = line.split()
    x.append(float(a)/60)
    y.append(calib_pid(float(b)))
    
y_err = 0.1*np.ones(len(x) + 1)

test = Fit(np.array(x),np.array(y),y_err)

def expup(t, A, B, tau): 
    return B + A*(1-np.exp(-(t-675)/tau))
test.curvefit(expup, output = True, p0 = [1,23,60], bounds = (675,806))

def expdown(t, A, B, tau): 
    return B + A*np.exp(-(t-807)/tau)
test.curvefit(expdown, output = True, p0 = [1,23,30], bounds = (807,960))

iplot(test.getPlotData(title="T_PID", x_axis="Time [min]", y_axis="Temp [°C]", connected = True, 
                       disp_error = False, x_range = [10*60,16.2*60], fitres = 500), show_link = False)
Fit function used: expup

degrees of freedom:         128
Least square sum:      4.16E+01
reduced chi-square:    3.25E-01

    params |     A            B            tau         
 __________|________________________________________
     value |   1.072E+00    2.375E+01    2.857E+01  
     sigma |   3.747E-02    4.036E-02    2.289E+00  

Fit function used: expdown

degrees of freedom:         150
Least square sum:      9.18E+01
reduced chi-square:    6.12E-01

    params |     A            B            tau         
 __________|________________________________________
     value |   1.133E+00    2.347E+01    3.311E+01  
     sigma |   3.485E-02    1.716E-02    2.320E+00  

Graph 7: Response of the pid-controller for a change of 50 bits in temperature

The time constants are:

$\tau_{up} = (28.6 \pm 2.3) min, \;\;\; \tau_{down} = (33.1 \pm 2.4) min$

It is also visible that the temperature does not overshoot and does not strongly oscillate.


I do another measurement to confirm the behaviour. This one is done at night (Temp to 500 Bits at 19:11 and back to 450 Bits at 22:45).

In [72]:
plot1 = THeePlot("./../data/PID_2017-02-21-22.THee", calib_pid, 1)
iplot(plot1.getPlotData(title="T_PID", x_axis="Time [h]", y_axis="Temp [°C]",connected = True,
                       x_range = [18,27]), show_link = False)

plot2 = THeePlot("./../data/Door_2017-02-21-22.THee", calib_door, 10)
iplot(plot2.getPlotData(title="T_Top_Door", x_axis="Time [h]", y_axis="Temp [°C]",connected = True,
                       x_range = [18,27]), show_link = False)

plot3 = THeePlot("./../data/Window_2017-02-21-22.THee", calib_window, 10)
iplot(plot3.getPlotData(title="T_Top_Window", x_axis="Time [h]", y_axis="Temp [°C]",connected = True,
                       x_range = [18,27]), show_link = False)

Graph 8: Second measurement of temperature under change of reference temperature

In [73]:
f = open("./../data/PID_2017-02-21-22.THee")
f.readline(); f.readline(); f.readline()
x,y = [],[]
for line in f:
    a,b = line.split()
    x.append(float(a)/60)
    y.append(calib_pid(float(b)))
    
y_err = 0.1*np.ones(len(x) + 1)
test = Fit(np.array(x),np.array(y),y_err)

def expup(t, A, B, tau): 
    return B + A*(1-np.exp(-(t-1152)/tau))
test.curvefit(expup, output = True, p0 = [2,23,60], bounds = (1062,1275))

def expdown(t, A, B, tau): 
    return B + A*np.exp(-(t-1365)/tau)
test.curvefit(expdown, output = True, p0 = [1,23,30], bounds = (1276,1550))

iplot(test.getPlotData(title="T_PID", x_axis="Time [min]", y_axis="Temp [°C]", connected = True, 
                       disp_error = False, x_range = [1055,2000], fitres = 500), show_link = False)
Fit function used: expup

degrees of freedom:         210
Least square sum:      4.50E+01
reduced chi-square:    2.14E-01

    params |     A            B            tau         
 __________|________________________________________
     value |   1.030E+00    2.376E+01    3.246E+01  
     sigma |   3.301E-02    3.421E-02    1.993E+00  

Fit function used: expdown

degrees of freedom:         271
Least square sum:      9.36E+01
reduced chi-square:    3.45E-01

    params |     A            B            tau         
 __________|________________________________________
     value |   1.081E+00    2.351E+01    2.536E+01  
     sigma |   3.972E-02    7.629E-03    1.499E+00  

Graph 9: Step response 2 of PID on PID temp. sensor

We get a similar value for the upwards slope. The downwards slope is slightly faster than before, which might be cause by lower outside temperature. We see here that some time after the temperature reaches the lower value again it starts to oscillate heavier.
This is also visible for the Door temperature, which we can also fit.

In [74]:
f = open("./../data/Door_2017-02-21-22.THee")
f.readline(); f.readline(); f.readline()
x,y = [],[]
i = 60
for line in f:
    if i == 60:
        a,b = line.split()
        x.append(float(a)/60)
        y.append(calib_door(float(b)))
        i = 0
    i += 1
    
y_err = 0.1*np.ones(len(x) + 1)

test = Fit(np.array(x),np.array(y),y_err)

def expup(t, A, B, tau): 
    return B + A*(1-np.exp(-(t-1152)/tau))
test.curvefit(expup, output = True, p0 = [2,23,60], bounds = (47,260))

def expdown(t, A, B, tau): 
    return B + A*np.exp(-(t-1365)/tau)
test.curvefit(expdown, output = True, p0 = [1,23,30], bounds = (261,600))

iplot(test.getPlotData(title="T_Top_Door", x_axis="Time [min]", y_axis="Temp [°C]", connected = True, 
                       disp_error = False, x_range = [1055,2000], fitres = 500), show_link = False)
Fit function used: expup

degrees of freedom:         210
Least square sum:      3.21E+01
reduced chi-square:    1.53E-01

    params |     A            B            tau         
 __________|________________________________________
     value |   1.143E+00    2.276E+01    3.471E+01  
     sigma |   3.235E-02    3.372E-02    1.929E+00  

Fit function used: expdown

degrees of freedom:         336
Least square sum:      1.22E+02
reduced chi-square:    3.63E-01

    params |     A            B            tau         
 __________|________________________________________
     value |   1.150E+00    2.247E+01    3.272E+01  
     sigma |   3.526E-02    6.930E-03    1.619E+00  

Graph 10: Step response 2 on Top_Door temperature sensor

Here we get nearly the same upwards time constant, but a lower downwards time constant.

3.2.3. Turning the lights on

At 11 o'clock I turn the lights on in the control room. The reaction at the different sensors is shown below, also the set values of the heater/cooler are shown.

In [75]:
plot1 = THeePlot("./../data/T_PID_2017-02-22.THee", calib_pid, 1)
iplot(plot1.getPlotData(title="T_PID", x_axis="Time [h]", y_axis="Temp [°C]",connected = True,
                       x_range = [7,14]), show_link = False)

plot2 = THeePlot("./../data/T_Top_Door_2017-02-22.THee", calib_door, 10)
iplot(plot2.getPlotData(title="T_Top_Door", x_axis="Time [h]", y_axis="Temp [°C]",connected = True,
                       x_range = [7,14]), show_link = False)

plot3 = THeePlot("./../data/T_Top_Window_2017-02-22.THee", calib_window, 10)
iplot(plot3.getPlotData(title="T_Top_Window", x_axis="Time [h]", y_axis="Temp [°C]",connected = True,
                       x_range = [7,14]), show_link = False)

Graph 11: Temperature response to light being turned on

In [76]:
f = open("./../data/light.dat")
x,y1,y2 = [],[],[]
for line in f:
    a,b,c = line.strip().split()
    x.append(int(a)/60/60); y1.append(float(b)); y2.append(float(c))
    
plot1 = Fit(x,y1)
plot2 = Fit(x,y2)
iplot(plot1.getPlotData(title="Heater", x_axis="Time [h]", y_axis="Set value [Bits]",connected = True), show_link = False)
iplot(plot2.getPlotData(title="Cooler", x_axis="Time [h]", y_axis="Set value [Bits]",connected = True), show_link = False)

Graph 12: Heater/cooler set values after light being turned on

The first 3 plots show that the temperature does oscillate more after turning the lights on, especially more to the higher temperatures.
Below we see that while the heater set value nearly stays the same the cooler starts cooling more after turning the lights on.

3.2.4. Temperature stabilization over several days

Over the weekend of the 24th to 27th I could make a measurement over two and a half days. The measurement starts on the 24th at around 16:30 and ends at the 27th at around 4:30. Here I can also do a histogram for the door and window temperatures, as there is no big jump in them.

In [77]:
plot1 = THeePlot("./../data/T_PID_2017-02-24-27.THee", calib_pid, 2)
iplot(plot1.getPlotData(title="T_PID", x_axis="Time [h]", y_axis="Temp [°C]",connected = True,
x_range = [16,77]), show_link = False)

plot2 = THeePlot("./../data/T_Top_Door_2017-02-24-27.THee", calib_door, 120)
iplot(plot2.getPlotData(title="T_Top_Door", x_axis="Time [h]", y_axis="Temp [°C]",connected = True,
                       x_range = [16,77]), show_link = False)

plot3 = THeePlot("./../data/T_Top_Window_2017-02-24-27.THee", calib_window, 120)
iplot(plot3.getPlotData(title="T_Top_Window", x_axis="Time [h]", y_axis="Temp [°C]",connected = True,
                       x_range = [16,77]), show_link = False)

yval1 = np.array(THeePlot("./../data/T_PID_2017-02-24-27.THee", calib_pid, 1).getPlotData()['data'][0]['y'])[480:-1140]
plot4 = Hist(yval1, binsize = 0.01)
iplot(plot4.getPlotData(title="T_PID Histogram", x_axis="Temperature", y_axis="Count"), show_link = False)
print("Mean = {:.3f}".format(np.mean(yval1)) + "K , " + " Std = {:.3f}".format(np.std(yval1)) + "K")

yval2 = np.array(THeePlot("./../data/T_Top_Door_2017-02-24-27.THee", calib_door, 1).getPlotData()['data'][0]['y'])
plot5 = Hist(yval2, binsize = 0.01)
iplot(plot5.getPlotData(title="T_Top_Door Histogram", x_axis="Temperature", y_axis="Count"), show_link = False)
print("Mean = {:.3f}".format(np.mean(yval2)) + "K , " + " Std = {:.3f}".format(np.std(yval2)) + "K")

yval3 = np.array(THeePlot("./../data/T_Top_Window_2017-02-24-27.THee", calib_window, 1).getPlotData()['data'][0]['y'])
plot6 = Hist(yval3, binsize = 0.02)
iplot(plot6.getPlotData(title="T_Top_Window Histogram", x_axis="Temperature", y_axis="Count"), show_link = False)
print("Mean = {:.3f}".format(np.mean(yval3)) + "K , " + " Std = {:.3f}".format(np.std(yval3)) + "K")
Mean = 23.514K ,  Std = 0.075K
Mean = 22.649K ,  Std = 0.070K
Mean = 24.927K ,  Std = 0.265K

Graph 13: Temperature stabilization over several days

We see that the temperature is fairly stable near the door and the PID temperature sensor ($\sigma = 0.076 K$ slightly higher than before), with not even half a degree deviations. The temperature near the window varies more, with a standard deviation of $\sigma = 0.267K$.
Its also visible that the PID sensor seems to show more oscillation on daytime and at the window there is a clear rise in temperature over the day.
What can also be seen is that the door temperature seems to be rather colder than the mean value, meaning it oscillates more to the cold side and the opposite is the case for the window temperature. This can also be seen by calculating the the weighted difference of points to the right to points to the left of the mean. If that number is positive it means that we have more points to the left.

In [78]:
i = 0
y1 = np.sort(yval1); mean1 = np.mean(y1)
while y1[i] < mean1:
    i+=1
print("PID:     {:.2E}".format((i-1/2*len(y1))/len(y1)) )

i = 0
y2 = np.sort(yval2); mean2 = np.mean(y2)
while y2[i] < mean2:
    i+=1
print("Door:    {:.2E}".format((i-1/2*len(y2))/len(y2)) )

i = 0
y3 = np.sort(yval3); mean3 = np.mean(y3)
while y3[i] < mean3:
    i+=1
print("Window: {:.2E}".format((i-1/2*len(y3))/len(y3)) )
PID:     9.67E-04
Door:    2.28E-02
Window: -1.25E-02

This could maybe be interpreted as seeing the effect of having the cooler near the door and the heater near the window (or light coming through the window).